/**
 * Copyright (C) 2012 Prof. Adriel Mota Ziesemer Jr.
 *
 * Este programa é software livre; você pode redistribuí-lo e/ou modificá-lo sob
 * os termos da Licença Pública Geral GNU, conforme publicada pela Free Software
 * Foundation; tanto a versão 2 da Licença como (a seu critério) qualquer versão
 * mais nova.
 *
 * Este programa é distribuído na expectativa de ser útil, mas SEM QUALQUER
 * GARANTIA; sem mesmo a garantia implícita de COMERCIALIZAÇÃO ou de ADEQUAÇÃO A
 * QUALQUER PROPÓSITO EM PARTICULAR. Consulte a Licença Pública Geral GNU para
 * obter mais detalhes.
 *
 * Contato: https://sites.google.com/site/profadrielziesemer/ Correio
 * Eletronico: aziesemer -no- gmail.com
 */
package negocio;

import java.awt.Image;
import java.awt.geom.Rectangle2D;
import java.util.Random;
import javax.swing.ImageIcon;

/**
 * Classe Animado da qual todos os objetos do jogo devem estender
 *
 * @author adrieljr
 */
public abstract class Animado {
    public enum Natureza {
        VOADOR, CAI, CHAO, FANTASMA
    };
    static protected Jogo jogo;
    protected Posicao posicao;
    private Posicao posAnterior;
    protected Image imagem;
    protected Natureza natureza; // se voa, cai, é chao ou fantasma
    protected int vida = getVidaInicial();
    protected boolean ehDestrutivel = true; // se é destrutivel ou nao 
    protected float coefRestituicao = 0.1f;   //0 empurra, 1 quica
    protected float coefAtrito = 0f;  // coef. desaceleração horizontal
    protected static final float AC_GRAVIDADE = 0.3f; //coef. aceleracao vertical
    private Animado chao = null;
    protected static Random r = new Random();

    public Animado(String nomeImagem, Jogo jogo, int posX, int posY, Natureza natureza) {
        this.imagem = new ImageIcon(this.getClass().getClassLoader().getResource(nomeImagem)).getImage();
        this.jogo = jogo;
        this.natureza = natureza;
        posAnterior = posicao = new Posicao(posX, posY, 0, 0);
    }

    // Método chamado a cada 25ms para realizar seu movimento
    public final void atualiza() {
        if (natureza == Natureza.CAI) {
            posicao.dY += AC_GRAVIDADE; //gravidade
            posicao.dX += Math.min(coefAtrito, Math.abs(posicao.dX)) * (posicao.dX > 0 ? -1 : +1); //atrito
            if (estaNoChao()) posicao.x += chao.posicao.dX;
        }
        logicaMovimento();
        posicao.x += posicao.dX;
        posicao.y += posicao.dY;
        chao = null;
        posAnterior = posicao.clone();
        logicaDestruicao();
    }

    // Método que deve ser sobrescrito para implementar as regras/lógica de movimento dos objetos
    protected void logicaMovimento() {
    }

    // Tira vida do objeto animado
    public boolean atinge(int valor) {
        if (ehDestrutivel && vida > 0) {
            vida -= valor;
            vida = Math.max(0, vida);
            vida = Math.min(this.getVidaInicial(), vida);
            if (vida == 0) {
                morre();
                return true;
            }
        }
        return false;
    }

    // Verifica se colidiu com outro objeto animado
    // Ao ser sobreescrito é necessário chamar super.colidiuCom(colidido)
    public void colidiuCom(Animado colidido) {
        Posicao outro = colidido.posAnterior;
        if (natureza != Natureza.CHAO && natureza != Natureza.FANTASMA && colidido.natureza != Natureza.FANTASMA) {
            if (Math.round(posicao.y + getHeight() - posicao.dY) <= Math.round(outro.y - outro.dY)) {
                // colisao por cima
                chao = (Animado) colidido;
                this.posicao.y = outro.y - this.getHeight();
                this.posicao.dY = (coefRestituicao * colidido.getMassa() * (outro.dY - posicao.dY) + (getMassa() * posicao.dY) + (colidido.getMassa() * outro.dY)) / (getMassa() + colidido.getMassa());
            } else if (Math.round(posicao.y - posicao.dY) >= Math.round(outro.y + colidido.getHeight() + posicao.dY)) {
                // colisao por baixo
                this.posicao.y = outro.y + colidido.getHeight();
                this.posicao.dY = (coefRestituicao * colidido.getMassa() * (outro.dY - posicao.dY) + (getMassa() * posicao.dY) + (colidido.getMassa() * outro.dY)) / (getMassa() + colidido.getMassa());
            } else if (Math.round(posicao.x + getWidth() - posicao.dX) <= Math.round(outro.x - outro.dX)) {
                // colisao pela esquerda
                this.posicao.x = outro.x - getWidth();
                this.posicao.dX = (coefRestituicao * colidido.getMassa() * (outro.dX - posicao.dX) + (getMassa() * posicao.dX) + (colidido.getMassa() * outro.dX)) / (getMassa() + colidido.getMassa());
            } else if (Math.round(posicao.x - posicao.dX + .5f) >= Math.round(outro.x + colidido.getWidth() - outro.dX)) {
                // colisao pela direita
                this.posicao.x = outro.x + colidido.getWidth();
                this.posicao.dX = (coefRestituicao * colidido.getMassa() * (outro.dX - posicao.dX) + (getMassa() * posicao.dX) + (colidido.getMassa() * outro.dX)) / (getMassa() + colidido.getMassa());
            }
        }
    }

    // Lógica para destruicao do objeto ao sair da tela
    protected void logicaDestruicao() {
        if (posicao.x < 0 || (natureza == Natureza.VOADOR && posicao.y + getHeight() < 0)
                || posicao.x > jogo.getLargura() || posicao.y > jogo.getAltura())
            vida = 0;
    }

    // É chamado para verificar se o objeto está morto, sendo necessário
    //executar sua eliminação da memória (dereferencia-lo)
    public boolean estahMorto() {
        return vida == 0;
    }

    public void morre() {
        vida = 0;
    }

    public boolean estaNoChao() {
        return chao != null;
    }

    public Rectangle2D getRetangulo() {
        return new Rectangle2D.Float(posicao.x, posicao.y, getWidth(), getHeight());
    }

    public Image getImagem() {
        return imagem;
    }

    public int getVida() {
        return vida;
    }

    public Posicao getAnterior() {
        return posAnterior;
    }

    public float getWidth() {
        return imagem.getWidth(null);
    }

    public float getHeight() {
        return imagem.getHeight(null);
    }

    public float getMassa() {
        return getHeight() * getWidth();
    }

    // Deve ser sobrescrito para retornar a vida inicial do objeto.
    // O valor deve ser maior que 1
    abstract public int getVidaInicial();

    /*
     * Vetor com a posicao do objeto e direcao do movimento
     */
    public class Posicao implements Cloneable {
        float x, y; // posicao x e y do objeto
        float dX;   // velocidade horizontal
        float dY;   // velocidade vertical

        public Posicao(float x, float y, float dX, float dY) {
            this.x = x;
            this.y = y;
            this.dX = dX;
            this.dY = dY;
        }

        @Override
        protected Posicao clone() {
            return new Posicao(x, y, dX, dY);
        }
    }
}